package com.cwd.arm.application.service.read;

import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.cwd.arm.application.dto.*;
import com.cwd.arm.application.dto.base.ReceiveDto;
import com.cwd.arm.application.dto.confirmIncome.ConfirmIncomeMonthAmountDto;
import com.cwd.arm.application.dto.confirmIncome.ProjectConfirmIncomeDto;
import com.cwd.arm.application.dto.project.*;
import com.cwd.arm.application.service.read.facade.*;
import com.cwd.arm.common.util.BigDecimalUtil;
import com.cwd.arm.common.util.DateUtil;
import com.cwd.arm.infrastructure.dao.ProjectDao;
import com.cwd.arm.infrastructure.po.ProjectPo;
import com.cwd.arm.infrastructure.query.*;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Repository;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.stream.Collectors;

/**
 * Description:项目
 * Created on 2021年11月16日
 *
 * @author yan
 */
@Repository
@Primary
public class ProjectReadServiceImpl implements ProjectReadService {

    @Autowired
    private ProjectDao projectDao;

    @Autowired
    private ReceiveNodeReadService receiveNodeReadService;

    @Autowired
    private ReceiveRecordReadService receiveRecordReadService;

    @Autowired
    private ConfirmIncomeReadService confirmIncomeReadService;

    @Autowired
    private PayReadService payReadService;

    @Autowired
    private TaxReadService taxReadService;

    @Autowired
    private ProjectMonthAmountTotalReadService projectMonthAmountTotalReadService;

    @Autowired
    private MonthAmountTotalReadService monthAmountTotalReadService;

    @Autowired
    private TargetConfirmAmountReadService targetConfirmAmountReadService;

    @Autowired
    private ProjectReceiveDirectorReadService projectReceiveDirectorReadService;

    @Override
    public ProjectDto selectById(Long id) {
        ProjectPo po = projectDao.getById(id);
        ProjectDto dto = convertToDto(po);
        return dto;
    }

    @Override
    public ProjectDetailDto selectDetailById(Long id) {
        ProjectDetailDto detailDto = new ProjectDetailDto();
        ProjectDto projectDto = selectById(id);
        detailDto.setProject(projectDto);
        detailDto.setNodeList(receiveNodeReadService.selectByProjectId(id));
        detailDto.setReceiveRecordList(receiveRecordReadService.selectByProjectId(id));
        detailDto.setConfirmIncomeList(confirmIncomeReadService.selectByProjectId(id));
        detailDto.setPayList(payReadService.selectByProjectId(id));
        detailDto.setTaxDtoList(taxReadService.selectByProjectId(id));
        detailDto.setProjectReceiveDirectorDtoList(projectReceiveDirectorReadService.selectByProjectId(id));
        return detailDto;
    }

    @Override
    public Page<ProjectDto> selectPage(ProjectDaoQuery query) {
        Page<ProjectPo> poPage = projectDao.selectPage(query);
        Page<ProjectDto> dtoPage = new Page<>();
        BeanUtils.copyProperties(poPage, dtoPage);
        List<ProjectDto> dtoList = poPage.getRecords().stream()
                .map(po -> convertToDto(po)).collect(Collectors.toList());
        dtoPage.setRecords(dtoList);
        return dtoPage;
    }

    @Override
    public List<ProjectDto> getProjectList(ProjectDaoQuery query) {
        List<ProjectPo> projectPoList = projectDao.getList(query);
        return projectPoList.stream().map(po -> convertToDto(po)).collect(Collectors.toList());
    }

    @Override
    public Page<ProjectReceivableAmountDto> selectReceivableAmountPage(ProjectDaoQuery query) throws Exception {
        Page<ProjectPo> poPage = projectDao.selectPage(query);
        Page<ProjectReceivableAmountDto> dtoPage = new Page<>();
        BeanUtils.copyProperties(poPage, dtoPage);
        List<ProjectPo> projectPoList = poPage.getRecords();
        List<ProjectReceivableAmountDto> projectReceivableAmountDtoList = getProjectReceivableAmountDtoList(query, projectPoList);
        dtoPage.setRecords(projectReceivableAmountDtoList);
        return dtoPage;
    }

    @Override
    public ProjectReceivableAmountTotalDto selectReceivableAmountTotal(Integer year) {
        ProjectReceivableAmountTotalDto amountTotalDto = new ProjectReceivableAmountTotalDto();

        amountTotalDto.setContractAmount(projectDao.getTotalContractAmount());

        BigDecimal totalReceivedAmount = projectDao.getTotalReceivedAmount();
        amountTotalDto.setReceivedAmount(totalReceivedAmount);

        BigDecimal totalConfirmAmount = projectDao.getTotalConfirmAmount();
        amountTotalDto.setTaxConfirmAmount(projectDao.getTotalConfirmAmount());

        amountTotalDto.setReceivableAmount(BigDecimalUtil.sub(totalConfirmAmount, totalReceivedAmount));

        //实际可收金额为项目所有节点closeAmount减去已到款，如果小于0就为0
        BigDecimal nodeCloseAmount = BigDecimalUtil.sub(projectDao.getTotalNodeCloseAmount(), totalReceivedAmount);
        if (nodeCloseAmount.compareTo(BigDecimal.ZERO) < 0) {
            nodeCloseAmount = BigDecimal.ZERO;
        }
        amountTotalDto.setNodeCloseAmount(nodeCloseAmount);

        List<MonthAmountTotalDto> monthAmountTotalDtoList = monthAmountTotalReadService.selectByYear(year);
        // TODO: 21-12-22 下一周节点 目前不做处理
        //季度
        setQAmount(amountTotalDto, monthAmountTotalDtoList);
        //本年收款节点
        setThisYearAmount(amountTotalDto, monthAmountTotalDtoList);
        //剩余季度收款
        setThisFySurplusQExpectReceivedAmount(amountTotalDto, monthAmountTotalDtoList);
        //次年收款节点
        List<MonthAmountTotalDto> nextYearMonthAmountTotalDtoList = monthAmountTotalReadService.selectByYear(year + 1);
        setNextYearAmount(amountTotalDto, nextYearMonthAmountTotalDtoList);
        return amountTotalDto;
    }

    @Override
    public List<ProjectReceivableAmountDto> getReceivableAmountList(ProjectDaoQuery daoQuery) throws Exception {
        List<ProjectPo> projectPoList = projectDao.getList(daoQuery);
        return getProjectReceivableAmountDtoList(daoQuery, projectPoList);
    }

    @Override
    public Page<ProjectReceivePlanDto> selectReceivePlanPage(ProjectDaoQuery query) throws Exception {
        Page<ProjectPo> poPage = projectDao.selectPage(query);
        Page<ProjectReceivePlanDto> dtoPage = new Page<>();
        BeanUtils.copyProperties(poPage, dtoPage);
        List<ProjectPo> projectPoList = poPage.getRecords();
        List<ProjectReceivePlanDto> projectReceivePlanDtoList = getProjectReceivePlanDtoList(query, projectPoList);
        dtoPage.setRecords(projectReceivePlanDtoList);
        return dtoPage;
    }

    @Override
    public List<ProjectReceivePlanDto> getReceivePlanList(ProjectDaoQuery query) throws Exception {
        List<ProjectPo> projectPoList = projectDao.getList(query);
        return getProjectReceivePlanDtoList(query, projectPoList);
    }

    @Override
    public Page<ProjectConfirmIncomeDto> selectProjectConfirmIncome(ProjectDaoQuery query) throws Exception {
        Page<ProjectPo> poPage = projectDao.selectPage(query);
        Page<ProjectConfirmIncomeDto> dtoPage = new Page<>();
        BeanUtils.copyProperties(poPage, dtoPage);
        List<ProjectPo> projectPoList = poPage.getRecords();
        List<ProjectConfirmIncomeDto> projectConfirmIncomeDtoList = getProjectConfirmIncomeDtoList(query, projectPoList);
        dtoPage.setRecords(projectConfirmIncomeDtoList);
        return dtoPage;
    }

    @Override
    public List<ProjectConfirmIncomeDto> getConfirmIncomeList(ProjectDaoQuery query) throws Exception {
        List<ProjectPo> projectPoList = projectDao.getUnCloseProjectList(query);
        return getProjectConfirmIncomeDtoList(query, projectPoList);
    }

    @Override
    public Page<ProjectCashFlowDto> selectProjectCashFlowPage(ProjectDaoQuery query) {
        Page<ProjectPo> poPage = projectDao.selectPage(query);
        Page<ProjectCashFlowDto> dtoPage = new Page<>();
        BeanUtils.copyProperties(poPage, dtoPage);
        List<ProjectPo> projectPoList = poPage.getRecords();
        List<ProjectCashFlowDto> projectCashFlowDtoList = getProjectCashFlowDtoList(query, projectPoList);
        dtoPage.setRecords(projectCashFlowDtoList);
        return dtoPage;
    }

    @Override
    public CashFlowTotalDto selectCashFlowTotal(Integer year) {
        List<MonthAmountTotalDto> monthAmountTotalDtoList = monthAmountTotalReadService.selectByYear(year);
        List<CashFlowMonthDto> cashFlowMonthDtoList = new ArrayList<>(12);
        for (int month = 1; month < 13; month++) {
            cashFlowMonthDtoList.add(getCashFlowMonthDto(monthAmountTotalDtoList, month));
        }
        CashFlowTotalDto cashFlowTotalDto = new CashFlowTotalDto();
        cashFlowTotalDto.setCashFlowMonthDtoList(cashFlowMonthDtoList);
        BigDecimal fyCashAmount = cashFlowMonthDtoList.stream().map(CashFlowMonthDto::getCashFlowAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
        cashFlowTotalDto.setFyCashAmount(fyCashAmount);
        BigDecimal fyActualPayAmount = cashFlowMonthDtoList.stream().map(CashFlowMonthDto::getActualPayAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
        cashFlowTotalDto.setFyActualPayAmount(fyActualPayAmount);
        BigDecimal fyReceivedAmount = cashFlowMonthDtoList.stream().map(CashFlowMonthDto::getReceivedAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
        cashFlowTotalDto.setFyReceivedAmount(fyReceivedAmount);
        return cashFlowTotalDto;
    }

    @Override
    public List<ProjectCashFlowDto> getCashList(ProjectDaoQuery daoQuery) {
        List<ProjectPo> projectPoList = projectDao.getUnCloseProjectList(daoQuery);
        return getProjectCashFlowDtoList(daoQuery, projectPoList);
    }

    @Async("armAsync")
    public Future<ProjectConfirmIncomeDto> convertToConfirmIncomeDto(ProjectDaoQuery query, ProjectPo projectPo) {
        ProjectConfirmIncomeDto projectConfirmIncomeDto = new ProjectConfirmIncomeDto();
        BeanUtils.copyProperties(projectPo, projectConfirmIncomeDto);

        //设置选中年份目标
        BigDecimal contractAmount = BigDecimalUtil.hasRecord(projectPo.getCloseAmount()) ? projectPo.getCloseAmount() : projectPo.getContractAmount();
        Integer year = query.getYear();
        BigDecimal fyTaxTargetAmount = BigDecimal.ZERO;
        TargetConfirmAmountDto targetConfirmAmountDto = targetConfirmAmountReadService.selectByProjectYear(projectPo.getId(), year);
        if (targetConfirmAmountDto != null) {
            fyTaxTargetAmount = targetConfirmAmountDto.getAmount();
        }
        projectConfirmIncomeDto.setFyTaxTargetAmount(fyTaxTargetAmount);
        BigDecimal fyTargetAmount = BigDecimalUtil.getWithTaxAmount(fyTaxTargetAmount, projectPo.getTaxPoint());
        projectConfirmIncomeDto.setFyTargetAmount(fyTargetAmount);
        BigDecimal fyTargetRate = BigDecimalUtil.div2(fyTaxTargetAmount, contractAmount);
        projectConfirmIncomeDto.setFyTargetRate(fyTargetRate);


        //设置实时信息(所有的确认收入，之前为今天和今天钱前的确认收入之和，因为用户不会输入今天后的所以忽律这个条件)
//        ConfirmIncomeDaoQuery daoQuery = new ConfirmIncomeDaoQuery();
//        daoQuery.setNow(new Date());
//        daoQuery.setProjectId(projectPo.getId());
//        daoQuery.setHasReceipt(true);
//        List<ConfirmIncomeDto> confirmIncomeDtoList = confirmIncomeReadService.selectByQuery(daoQuery);
//        BigDecimal rtTaxAmount = confirmIncomeDtoList.stream().map(ConfirmIncomeDto::getConfirmAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
        BigDecimal rtTaxAmount = projectPo.getConfirmAmount();
        projectConfirmIncomeDto.setRtTaxAmount(rtTaxAmount);
        BigDecimal rtAmount = BigDecimalUtil.getWithTaxAmount(rtTaxAmount, projectPo.getTaxPoint());
        projectConfirmIncomeDto.setRtAmount(rtAmount);
        BigDecimal rtRate = BigDecimalUtil.div2(rtTaxAmount, contractAmount);
        projectConfirmIncomeDto.setRtRate(rtRate);

        //设置剩余金额
//        BigDecimal targetAmount = projectPo.getContractAmount();
//        if (BigDecimalUtil.hasRecord(projectPo.getCloseAmount())) {
//            targetAmount = projectPo.getCloseAmount();
//        }
        BigDecimal surplusTaxAmount = BigDecimalUtil.sub(contractAmount, rtTaxAmount);
        projectConfirmIncomeDto.setSurplusTaxAmount(surplusTaxAmount);
        projectConfirmIncomeDto.setSurplusAmount(BigDecimalUtil.getWithTaxAmount(surplusTaxAmount, projectPo.getTaxPoint()));

        //设置年度确认完成
        List<ConfirmIncomeMonthAmountDto> monthAmountDtoList = new ArrayList<>(12);
        List<ProjectMonthAmountTotalDto> monthAmountTotalDtoList = projectMonthAmountTotalReadService.selectByProjectIdYear(projectPo.getId(), query.getYear());
        BigDecimal totalConfirmAmount = BigDecimal.ZERO;
        for (int month = 1; month < 13; month++) {
            ConfirmIncomeMonthAmountDto confirmIncomeMonthAmountDto = getProjectMonthAmountDto(monthAmountTotalDtoList, month, projectPo.getContractAmount());
            totalConfirmAmount = totalConfirmAmount.add(confirmIncomeMonthAmountDto.getAmount());
            confirmIncomeMonthAmountDto.setRate(BigDecimalUtil.div2(totalConfirmAmount, fyTaxTargetAmount));
            monthAmountDtoList.add(confirmIncomeMonthAmountDto);
        }
        projectConfirmIncomeDto.setMonthAmountDtoList(monthAmountDtoList);
        return AsyncResult.forValue(projectConfirmIncomeDto);
    }

    @Async("armAsync")
    public Future<ProjectReceivableAmountDto> convertToProjectReceivableAmountDto(ProjectPo projectPo, ProjectDaoQuery query) {
        ProjectReceivableAmountDto amountDto = convertToAmountDto(projectPo);

        //计算下一周节点
        List<Date> week = DateUtil.getNextWeekDays();
        ReceiveNodeDaoQuery daoQuery = new ReceiveNodeDaoQuery();
        daoQuery.setProjectId(projectPo.getId());
        List<ReceiveNodeDto> receiveNodeDtoList = receiveNodeReadService.selectByQuery(daoQuery);
//        daoQuery.setEndDateList(week);
//        List<ReceiveNodeDto> receiveNodeDtoList = receiveNodeReadService.selectByQuery(daoQuery);
        if (CollectionUtils.isNotEmpty(receiveNodeDtoList)) {
            ReceiveNodeDto receiveNodeDto = receiveNodeDtoList.stream()
                    .filter(dto -> isThisWeek(dto.getEndDate(), week)).findAny().orElse(null);
            //取其中一个节点
//            ReceiveNodeDto receiveNodeDto = receiveNodeDtoList.get(0);
            if (receiveNodeDto != null) {
                amountDto.setNextWeekRate(receiveNodeDto.getCompletionRate());
                amountDto.setNextWeekNodeCondition(receiveNodeDto.getNodeCondition());
                amountDto.setNextWeekEndDate(receiveNodeDto.getEndDate());
                amountDto.setNextWeekReceivableAmount(receiveNodeDto.getReceivableAmount());
            }

        }

        //本年收款节点
        ProjectMonthAmountTotalDaoQuery projectMonthAmountTotalDaoQuery = new ProjectMonthAmountTotalDaoQuery();
        projectMonthAmountTotalDaoQuery.setFyYear(query.getYear());
        projectMonthAmountTotalDaoQuery.setProjectId(projectPo.getId());
        List<ProjectMonthAmountTotalDto> projectMonthAmountTotalDtoList = projectMonthAmountTotalReadService.selectByQuery(projectMonthAmountTotalDaoQuery);
        List<MonthAmountTotalDto> monthTotalList = projectMonthAmountTotalDtoList.stream().map(m -> (MonthAmountTotalDto) m).collect(Collectors.toList());
        setQAmount(amountDto, monthTotalList);

        //本年
        setThisYearAmount(amountDto, monthTotalList);

        //次年
        projectMonthAmountTotalDaoQuery.setFyYear(query.getYear() + 1);
        List<ProjectMonthAmountTotalDto> nextProjectMonthAmountTotalDtoList = projectMonthAmountTotalReadService.selectByQuery(projectMonthAmountTotalDaoQuery);
        List<MonthAmountTotalDto> nextFyMonthTotalList = nextProjectMonthAmountTotalDtoList.stream().map(m -> (MonthAmountTotalDto) m).collect(Collectors.toList());
        setNextYearAmount(amountDto, nextFyMonthTotalList);

        return AsyncResult.forValue(amountDto);
    }

    @Async("armAsync")
    public Future<ProjectReceivePlanDto> convertToProjectReceivePlanDto(ProjectPo projectPo, ProjectDaoQuery query) {
        ProjectReceivePlanDto planDto = convertToPlanDto(projectPo);
        int month = query.getMonth();
        int year = DateUtil.getThisFy();
        if (DateUtil.getThisMonth() > month) {//处理隔年的情况
            year = year + 1;
        }
        ProjectMonthAmountTotalDto projectMonthAmountTotalDto
                = projectMonthAmountTotalReadService.selectByYearAndMonth(projectPo.getId(), year, query.getMonth());
        if (projectMonthAmountTotalDto != null) {
            planDto.setReceivedAmount(projectMonthAmountTotalDto.getReceivableAmount());
        }
        return AsyncResult.forValue(planDto);
    }

    private void setThisFySurplusQExpectReceivedAmount(ProjectReceivableAmountTotalDto amountTotalDto, List<MonthAmountTotalDto> monthAmountTotalDtoList) {
        int thisQ = DateUtil.getThisQ();
        BigDecimal thisFySurplusQExpectReceivedAmount = monthAmountTotalDtoList.stream()
                .filter(totalDto -> DateUtil.getQ(totalDto.getFyMonth()) > thisQ)
                .map(MonthAmountTotalDto::getReceivableAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
        amountTotalDto.setThisFySurplusQExpectReceivedAmount(thisFySurplusQExpectReceivedAmount);
    }

    private CashFlowMonthDto getCashFlowMonthDto(List<MonthAmountTotalDto> monthAmountTotalDtoList, int month) {
        CashFlowMonthDto cashFlowMonthDto = new CashFlowMonthDto();
        MonthAmountTotalDto monthAmountTotalDto = monthAmountTotalDtoList.stream().filter(monthTotal -> (monthTotal.getFyMonth().equals(month))).findAny().orElse(null);
        cashFlowMonthDto.setMonth(month);
        if (monthAmountTotalDto != null) {
            cashFlowMonthDto.setActualPayAmount(monthAmountTotalDto.getActualPayAmount());
            cashFlowMonthDto.setReceivedAmount(monthAmountTotalDto.getReceivedAmount());
            cashFlowMonthDto.setCashFlowAmount(BigDecimalUtil.sub(monthAmountTotalDto.getReceivedAmount(), monthAmountTotalDto.getActualPayAmount()));
        }
        return cashFlowMonthDto;
    }

    private ConfirmIncomeMonthAmountDto getProjectMonthAmountDto(List<ProjectMonthAmountTotalDto> totalDtoList,
                                                                 int month, BigDecimal contractAmount) {
        ConfirmIncomeMonthAmountDto confirmIncomeMonthAmountDto = new ConfirmIncomeMonthAmountDto();
        confirmIncomeMonthAmountDto.setMonth(month);
        ProjectMonthAmountTotalDto projectMonthAmountTotalDto = totalDtoList.stream().filter(dto -> (dto.getFyMonth() == month)).findAny().orElse(null);
        if (projectMonthAmountTotalDto != null) {
            BigDecimal amount = projectMonthAmountTotalDto.getConfirmAmount();
            confirmIncomeMonthAmountDto.setAmount(amount);
//            totalConfirmAmount = totalConfirmAmount.add(amount);
//            BigDecimal rtRate = BigDecimalUtil.div2(totalConfirmAmount, contractAmount);
//            confirmIncomeMonthAmountDto.setRate(rtRate);
        }
        return confirmIncomeMonthAmountDto;
    }

    private ProjectCashFlowMonthDto getProjectCashFlowMonthDto(List<ProjectMonthAmountTotalDto> totalDtoList, int month, BigDecimal contractAmount) {
        ProjectCashFlowMonthDto projectCashFlowMonthDto = new ProjectCashFlowMonthDto();
        projectCashFlowMonthDto.setMonth(month);
        ProjectMonthAmountTotalDto projectMonthAmountTotalDto = totalDtoList.stream().filter(dto -> (dto.getFyMonth() == month)).findAny().orElse(null);
        if (projectMonthAmountTotalDto != null) {
            BigDecimal actualPayAmount = projectMonthAmountTotalDto.getActualPayAmount();
            projectCashFlowMonthDto.setActualPayAmount(actualPayAmount);
            BigDecimal receivedAmount = projectMonthAmountTotalDto.getReceivedAmount();
            projectCashFlowMonthDto.setReceivedAmount(receivedAmount);
            projectCashFlowMonthDto.setCashFlowAmount(BigDecimalUtil.sub(receivedAmount, actualPayAmount));
        }
        return projectCashFlowMonthDto;
    }

    private ProjectReceivableAmountDto convertToAmountDto(ProjectPo projectPo) {
        ProjectReceivableAmountDto amountDto = new ProjectReceivableAmountDto();
        BeanUtils.copyProperties(projectPo, amountDto);
        amountDto.setTaxConfirmAmount(projectPo.getConfirmAmount());
        //应收金额从财务学角度来理解应为“确认收入金额-已收金额”
        amountDto.setReceivableAmount(BigDecimalUtil.sub(projectPo.getConfirmAmount(), projectPo.getReceivedAmount()));
        BigDecimal nodeCloseAmount = BigDecimalUtil.sub(projectPo.getNodeCloseAmount(), projectPo.getReceivedAmount());
        if (nodeCloseAmount.compareTo(BigDecimal.ZERO) < 0) {
            nodeCloseAmount = BigDecimal.ZERO;
        }
        amountDto.setNodeCloseAmount(nodeCloseAmount);
        return amountDto;
    }

    private ProjectReceivePlanDto convertToPlanDto(ProjectPo projectPo) {
        ProjectReceivePlanDto dto = new ProjectReceivePlanDto();
        BeanUtils.copyProperties(projectPo, dto);
        dto.setTotalReceivableAmount(projectPo.getReceivableAmount());
        dto.setTotalReceivedAmount(projectPo.getReceivedAmount());
        return dto;
    }

    private BigDecimal getQAmount(List<MonthAmountTotalDto> monthTotalList, int q) {
        return monthTotalList.stream().filter(monthTotal -> monthTotal != null && DateUtil.getQ(monthTotal.getFyMonth()) == q).map(MonthAmountTotalDto::getReceivedAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
    }

    private ProjectDto convertToDto(ProjectPo po) {
        if (po == null) {
            return null;
        }
        ProjectDto dto = new ProjectDto();
        BeanUtils.copyProperties(po, dto);
        ProjectMonthAmountTotalDaoQuery daoQuery = new ProjectMonthAmountTotalDaoQuery();
        daoQuery.setProjectId(po.getId());
        int thisFy = DateUtil.getThisFy();
        daoQuery.setFyYear(thisFy);
        List<ProjectMonthAmountTotalDto> thisYearProjectMonthAmountTotalDtoList = projectMonthAmountTotalReadService.selectByQuery(daoQuery);
        int thisQ = DateUtil.getThisQ();
        //剩余季度收款
        BigDecimal thisFySurplusQExpectReceivedAmount = thisYearProjectMonthAmountTotalDtoList.stream()
                .filter(totalDto -> DateUtil.getQ(totalDto.getFyMonth()) > thisQ)
                .map(ProjectMonthAmountTotalDto::getReceivableAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
        dto.setThisFySurplusQExpectReceivedAmount(thisFySurplusQExpectReceivedAmount);
        //次年总计
        int nextFy = thisFy + 1;
        daoQuery.setFyYear(nextFy);
        List<ProjectMonthAmountTotalDto> nextYearProjectMonthAmountTotalDtoList = projectMonthAmountTotalReadService.selectByQuery(daoQuery);
        BigDecimal lastFyExpectReceivedAmount = nextYearProjectMonthAmountTotalDtoList.stream()
                .map(ProjectMonthAmountTotalDto::getReceivableAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
        dto.setLastFyExpectReceivedAmount(lastFyExpectReceivedAmount);
        //合同收款比例
        BigDecimal contractRate = BigDecimal.ZERO;
        //确认收入进度与合同比例
        BigDecimal confirmContractRate = BigDecimal.ZERO;
        BigDecimal contractAmount = BigDecimalUtil.hasRecord(dto.getCloseAmount()) ? dto.getCloseAmount() : dto.getContractAmount();
        if (contractAmount != null && contractAmount.compareTo(BigDecimal.ZERO) != 0) {
            contractRate = BigDecimalUtil.div2(dto.getReceivedAmount(), contractAmount);
            confirmContractRate = BigDecimalUtil.div2(dto.getConfirmAmount(), contractAmount);
        }
        dto.setContractRate(contractRate);
        dto.setConfirmContractRate(confirmContractRate);
        //确认收入金额与应收金额差(废弃字段)
        BigDecimal confirmReceivableDiffAmount = dto.getConfirmAmount().subtract(dto.getReceivableAmount());
        dto.setConfirmReceivableDiffAmount(confirmReceivableDiffAmount);
        //应收金额(确认收入－已收金额)
        dto.setReceivableAmount(BigDecimalUtil.sub(po.getConfirmAmount(), po.getReceivedAmount()));
        //工期
        if (dto.getStartDate() != null && dto.getEndDate() != null) {
            dto.setWorkDay(DateUtil.differentDaysByMillisecond(dto.getStartDate(), dto.getEndDate()) + 1);
        }
        //结算周期
        if (dto.getEndDate() != null && dto.getCloseDate() != null) {
            dto.setCloseCycle(DateUtil.differentDaysByMillisecond(dto.getEndDate(), dto.getCloseDate()) + 1);
        }
        //开票总额
        dto.setTaxAmount(BigDecimalUtil.add(dto.getReceivedAmount(), dto.getTaxUnreceiveAmount()));
        return dto;
    }

    private void setNextYearAmount(ReceiveDto receiveDto, List<MonthAmountTotalDto> nextFyMonthTotalList) {
        BigDecimal nextFyTotalAmount = nextFyMonthTotalList.stream().map(MonthAmountTotalDto::getReceivableAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
        receiveDto.setLastFyTotalAmount(nextFyTotalAmount);
    }

    private void setThisYearAmount(ReceiveDto receiveDto, List<MonthAmountTotalDto> monthTotalList) {
        BigDecimal thisFyTotalAmount = monthTotalList.stream().map(MonthAmountTotalDto::getReceivedAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
        receiveDto.setThisFyTotalAmount(thisFyTotalAmount);
    }

    private void setQAmount(ReceiveDto receiveDto, List<MonthAmountTotalDto> baseMonthTotalList) {
        receiveDto.setThisFyQ1Amount(getQAmount(baseMonthTotalList, DateUtil.Q1));
        receiveDto.setThisFyQ2Amount(getQAmount(baseMonthTotalList, DateUtil.Q2));
        receiveDto.setThisFyQ3Amount(getQAmount(baseMonthTotalList, DateUtil.Q3));
        receiveDto.setThisFyQ4Amount(getQAmount(baseMonthTotalList, DateUtil.Q4));
    }

    private boolean isThisWeek(Date date, List<Date> dateList) {
        Date findDate = dateList.stream().filter(date1 -> DateUtil.isTheSameDate(date1, date)).findAny().orElse(null);
        if (findDate == null) {
            return false;
        }
        return true;
    }

    private List<ProjectCashFlowDto> getProjectCashFlowDtoList(ProjectDaoQuery query, List<ProjectPo> projectPoList) {
        List<ProjectCashFlowDto> projectCashFlowDtoList = new ArrayList<>();
        for (ProjectPo projectPo : projectPoList) {
            ProjectCashFlowDto projectCashFlowDto = new ProjectCashFlowDto();
            BeanUtils.copyProperties(projectPo, projectCashFlowDto);
            projectCashFlowDto.setWithLastReceivedAmount(projectPo.getReceivedAmount());
            projectCashFlowDto.setWithLastYearActualPayAmount(projectPo.getActualPayAmount());
            projectCashFlowDtoList.add(projectCashFlowDto);

            //设置项目每月现金流合计
            List<ProjectMonthAmountTotalDto> monthAmountTotalDtoList = projectMonthAmountTotalReadService.selectByProjectIdYear(projectPo.getId(), query.getYear());
            List<ProjectCashFlowMonthDto> cashFlowMonthDtoList = new ArrayList<>(12);
            for (int month = 1; month < 13; month++) {
                cashFlowMonthDtoList.add(getProjectCashFlowMonthDto(monthAmountTotalDtoList, month, projectPo.getContractAmount()));
            }
            projectCashFlowDto.setCashFlowMonthDtoList(cashFlowMonthDtoList);

            BigDecimal fyCashAmount = cashFlowMonthDtoList.stream().map(ProjectCashFlowMonthDto::getCashFlowAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
            projectCashFlowDto.setFyCashAmount(fyCashAmount);
            BigDecimal fyActualPayAmount = cashFlowMonthDtoList.stream().map(ProjectCashFlowMonthDto::getActualPayAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
            projectCashFlowDto.setFyActualPayAmount(fyActualPayAmount);
            BigDecimal fyReceivedAmount = cashFlowMonthDtoList.stream().map(ProjectCashFlowMonthDto::getReceivedAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
            projectCashFlowDto.setFyReceivedAmount(fyReceivedAmount);
        }
        return projectCashFlowDtoList;
    }

    private List<ProjectReceivableAmountDto> getProjectReceivableAmountDtoList(ProjectDaoQuery query, List<ProjectPo> projectPoList) throws InterruptedException, ExecutionException {
        List<ProjectReceivableAmountDto> projectReceivableAmountDtoList = new ArrayList<>(10);
        List<Future<ProjectReceivableAmountDto>> futureList = new ArrayList<>(10);
        for (ProjectPo projectPo : projectPoList) {
            Future<ProjectReceivableAmountDto> future = convertToProjectReceivableAmountDto(projectPo, query);
            futureList.add(future);
        }
        for (Future<ProjectReceivableAmountDto> future : futureList) {
            ProjectReceivableAmountDto projectReceivableAmountDto = future.get();
            projectReceivableAmountDtoList.add(projectReceivableAmountDto);
        }
        return projectReceivableAmountDtoList;
    }

    private List<ProjectConfirmIncomeDto> getProjectConfirmIncomeDtoList(ProjectDaoQuery query, List<ProjectPo> projectPoList) throws Exception {
        List<ProjectConfirmIncomeDto> projectConfirmIncomeDtoList = new ArrayList<>(10);
        List<Future<ProjectConfirmIncomeDto>> futureList = new ArrayList<>(10);
        for (ProjectPo projectPo : projectPoList) {
            Future<ProjectConfirmIncomeDto> future = convertToConfirmIncomeDto(query, projectPo);
            futureList.add(future);
        }
        for (Future<ProjectConfirmIncomeDto> future : futureList) {
            ProjectConfirmIncomeDto projectConfirmIncomeDto = future.get();
            projectConfirmIncomeDtoList.add(projectConfirmIncomeDto);
        }
        return projectConfirmIncomeDtoList;
    }

    private List<ProjectReceivePlanDto> getProjectReceivePlanDtoList(ProjectDaoQuery query, List<ProjectPo> projectPoList) throws InterruptedException, java.util.concurrent.ExecutionException {
        List<ProjectReceivePlanDto> projectReceivePlanDtoList = new ArrayList<>(10);
        List<Future<ProjectReceivePlanDto>> futureList = new ArrayList<>(10);
        for (ProjectPo projectPo : projectPoList) {
            Future<ProjectReceivePlanDto> future = convertToProjectReceivePlanDto(projectPo, query);
            futureList.add(future);
        }
        for (Future<ProjectReceivePlanDto> future : futureList) {
            ProjectReceivePlanDto projectReceivePlanDto = future.get();
            projectReceivePlanDtoList.add(projectReceivePlanDto);
        }
        return projectReceivePlanDtoList;
    }
}
